#region (c) 2014 Nguyễn, M. Hải  - All rights reserved
/*
Simplified BSD License (BSD)
Copyright (c) 2014, Nguyễn, M. Hải ( www.Minh-Hai.com ). All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice, this 
      list of conditions and the following disclaimer.

    * Redistributions in binary form must reproduce the above copyright notice, 
      this list of conditions and the following disclaimer in the documentation 
      and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.
*/
#endregion

#region Record of Change
//             							CHANGE HISTORY
// -------------------------------------------------------------------------------------
// |   DATE    | DEVELOPER  | DESCRIPTION                                              |
// -------------------------------------------------------------------------------------
// | 04-Oct-12 | HaiNM      | First creation.                                          |
// -------------------------------------------------------------------------------------
//
#endregion

#region Record of Audit
//             							REVIEW HISTORY
// -------------------------------------------------------------------------------------
// |   DATE    | REVIEWER   | NOTES                                                    |
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
//
#endregion

#region Using directives
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Input;
using System.Windows.Controls;
using System.Windows.Navigation;
using System.Collections.Generic;

using Thn;
using Thn.Presentation.Controls;
#endregion

namespace Thn.Presentation.Controls
{
    /// <summary>
    /// 
    /// </summary>
#if DEBUG
    public
#else
    public
#endif
 partial class Table : Grid
    {
        #region Fields
        Dictionary<int, TableCell> mCells = new Dictionary<int, TableCell>();
        int mCurrentColumn = 0;
        int mCurrentRow = 0;
        Border mBorder;
        #endregion

        #region Properties

        #region Columns
        ///// <summary>
        ///// Gets or sets description.
        ///// </summary>
        //[DefaultValue(null)]
        //public List<TableColumn> Columns
        //{
        //    get { return (List<TableColumn>)GetValue(ColumnsProperty); }
        //    set { SetValue(ColumnsProperty, value); }
        //}

        ///// <summary>
        ///// Backing dependency property for Columns.
        ///// </summary>
        //public static readonly DependencyProperty ColumnsProperty =
        //    DependencyProperty.Register(
        //    "Columns"
        //    , typeof(List<TableColumn>)
        //    , typeof(Table)
        //    , new PropertyMetadata(new List<TableColumn>(), ColumnsPropertyChanged)
        //    );

        ///// <summary>
        ///// Invoked when value of property Columns is changed.
        ///// </summary>
        //protected virtual void OnColumnsChanged(DependencyPropertyChangedEventArgs e)
        //{
        //}

        ///// <summary>
        ///// Static handler for listening to changes of ColumnsProperty.
        ///// </summary>
        //private static void ColumnsPropertyChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
        //{
        //    Table obj = (Table)target;

        //    //update target object
        //    obj.OnColumnsChanged(e);
        //}
        #endregion

        #region Columns
        private List<TableColumn> mColumns = new List<TableColumn>();
        /// <summary>
        /// Gets or sets description.
        /// </summary>
        public List<TableColumn> Columns
        {
            get { return mColumns; }
            set { mColumns = value; }
        }
        #endregion

        #region Rows
        private List<TableRow> mRows = new List<TableRow>();
        /// <summary>
        /// Gets or sets description.
        /// </summary>
        public List<TableRow> Rows
        {
            get { return mRows; }
            set { mRows = value; }
        }
        #endregion

        #region Default Row Height
        private double mDefaultRowHeight = 25.0;
        /// <summary>
        /// Gets or sets description.
        /// </summary>
        public double DefaultRowHeight
        {
            get { return mDefaultRowHeight; }
            set { mDefaultRowHeight = value; }
        }
        #endregion

        #region Border Stroke
        private Brush mBorderStroke = new SolidColorBrush(Colors.Black);
        /// <summary>
        /// Gets or sets description.
        /// </summary>
        public Brush BorderStroke
        {
            get { return mBorderStroke; }
            set { mBorderStroke = value; }
        }
        #endregion

        #region TotalHeight
        /// <summary>
        /// Gets or sets description.
        /// </summary>
        public double TotalHeight
        {
            get
            {
                double result = 0;
                double rowHeight;
                foreach (var item in mRows)
                {
                    rowHeight = item.Height >= 0 ? item.Height : mDefaultRowHeight;
                    result += rowHeight;
                }
                return result;
            }
        }
        #endregion

        #region TotalWidth
        /// <summary>
        /// Gets or sets description.
        /// </summary>
        public double TotalWidth
        {
            get
            {
                double result = 0;
                foreach (var item in mColumns)
                {
                    result += item.Width;
                }
                return result;
            }
        }
        #endregion

        #region Indexer
        /// <summary>
        /// Gets or sets a cell based on its column and row index.
        /// </summary>
        public TableCell this[int column, int row]
        {
            get { return GetCell(column, row); }
            set
            {
                int id = GetCellId(column, row);
                mCells[id] = value;
            }
        }
        #endregion

        #endregion

        #region UI Events processing
        #endregion

        #region Create Cell
        TableCell CreateCell()
        {
            TableCell result = new TableCell();
            Border b = new Border();
            b.BorderBrush = mBorderStroke;
            b.BorderThickness = new Thickness(1.0);
            b.Margin = new Thickness(-0.5);
            result.Border = b;
            return result;
        }
        #endregion

        #region Get Cell Id
        int GetCellId(int column, int row)
        {
            int result = (column << 16)
                        | row
                        ;
            return result;
        }
        #endregion

        #region Get Cell
        public TableCell GetCell(int column, int row)
        {
            int id = GetCellId(column, row);

            //ensure enough row and column
            if (column >= Columns.Count)
            {
                EnsureEnoughColumns(column + 1);
            }
            #region Defensive tracing
            else
            {
                //already big enough
            }
            #endregion

            if (row >= Rows.Count)
            {
                EnsureEnoughRows(row + 1);
            }
            #region Defensive tracing
            else
            {
                //already big enough
            }
            #endregion

            return GetCell(id);
        }
        /// <summary>
        /// Gets a cell by its ID.
        /// </summary>
        TableCell GetCell(int id)
        {
            TableCell result;

            if (mCells.ContainsKey(id))
            {
                result = mCells[id];
            }
            else
            {
                result = CreateCell();                    
                mCells[id] = result;
            }

            return result;
        }
        #endregion

        #region Set Content
        public TableCell SetContent(int column, int row, object content)
        {
            return SetContent(column, row, content, string.Empty);
        }
        public TableCell SetContent(int column, int row, object content, string format)
        {
            TableCell cell;
            UIElement child;

            //Handle content
            if (content is UIElement)
            {
                #region Already an UI Element -> use it
                child = (UIElement)content;
                #endregion
            }
            else if ((content is double) || (content is decimal) || (content is int))
            {
                #region Numeric value -> use NumberLabel
                NumberLabel nb = new NumberLabel();
                if (content is double)
                {
                    nb.Value = (decimal)((double)content);
                }
                else if (content is int)
                {
                    nb.Value = (decimal)((int)content);
                }
                else
                {
                    nb.Value = (decimal)content;
                }
                child = nb;
                #endregion
            }
            else
            {
                #region Anything else -> consider as string -> use TextBlock
                TextBlock tb = new TextBlock();
                tb.TextWrapping = TextWrapping.Wrap;
                tb.Text = string.Format("{0}", content);
                tb.Margin = new Thickness(5);
                TableColumn tc = mColumns[column];
                tb.SetValue(HorizontalAlignmentProperty, tc.HorizontalAlignment);
                tb.SetValue(VerticalAlignmentProperty, VerticalAlignment.Center);
                child = tb;
                #endregion
            }

            //prepare the cell
            cell = GetCell(column, row);
            cell.Child = child;
            child.SetValue(Grid.ColumnProperty, column);
            child.SetValue(Grid.RowProperty, row);
            if (!string.IsNullOrEmpty(format)) cell.Format = format;

            return cell;
        }
        #endregion

        #region Set
        /// <summary>
        /// Set content of current cell and advance row.
        /// </summary>
        public TableCell Set(object content)
        {
            TableCell result = SetContent(mCurrentColumn, mCurrentRow, content);
            mCurrentRow++;
            return result;
        }

        /// <summary>
        /// Set content of current cell and advance row.
        /// </summary>
        public TableCell Set(object content, string format)
        {
            TableCell result = SetContent(mCurrentColumn, mCurrentRow, content, format);
            mCurrentRow++;
            return result;
        }
        #endregion

        #region Ensure Enough Columns
        void EnsureEnoughColumns(int columns)
        {
            if (columns > Columns.Count)
            {
                int required = columns - Columns.Count;
                for (int i = 0; i < required; i++)
                {
                    Columns.Add(new TableColumn());
                }
            }
            #region Defensive tracing
            else
            {
                //already big enough
            }
            #endregion
        }
        #endregion

        #region Ensure Enough Rows
        void EnsureEnoughRows(int rows)
        {
            if (rows > Rows.Count)
            {
                int required = rows - Rows.Count;
                for (int i = 0; i < required; i++)
                {
                    Rows.Add(new TableRow());
                }
            }
            #region Defensive tracing
            else
            {
                //already big enough
            }
            #endregion
        }
        #endregion

        #region Render
        public void Render()
        {
            Grid g = this;
            g.Children.Clear();
            GenerateColumns();
            GenerateRows();
            GenerateCells();
            RenderBorder();
            //RenderLines();
        }
        #endregion

        #region Render Border
        void RenderBorder()
        {
            Grid g = this;
            //g.Children.Add(mBorder);
            //mBorder.SetValue(Grid.ColumnSpanProperty, mColumns.Count);
            //mBorder.SetValue(Grid.RowSpanProperty, mRows.Count);
        }
        #endregion

        #region Render Lines
        void RenderLines()
        {
            Grid g = this;
            List<TableColumn> columns = this.Columns;
            List<TableRow> rows = this.Rows;
            int columnCount = columns.Count;
            int rowCount = rows.Count + 1;
            double thickness = 1.0;
            double totalHeight = this.TotalHeight;
            double totalWidth = this.TotalWidth;

            #region Render vertical lines
            for (int i = 0; i <= columnCount; i++)
            {
                Line l = new Line();
                l.Stroke = mBorderStroke;
                l.StrokeThickness = thickness;
                l.SetValue(Grid.RowSpanProperty, rowCount);
                l.SetValue(Grid.ColumnProperty, i);
                l.X1 = 0.5;
                l.X2 = 0.5;
                l.Y2 = totalHeight;
                g.Children.Add(l);
            }
            #endregion

            #region Render horizontal lines
            for (int i = 0; i <= rowCount; i++)
            {
                Line l = new Line();
                l.Stroke = mBorderStroke;
                l.StrokeThickness = thickness;
                l.SetValue(Grid.ColumnSpanProperty, columnCount);
                l.SetValue(Grid.RowProperty, i);
                l.X2 = totalWidth;
                l.Y1 = 0.5;
                l.Y2 = 0.5;
                g.Children.Add(l);
            }
            #endregion
        }
        #endregion

        #region Generate Columns
        void GenerateColumns()
        {
            Grid g = this;
            g.ColumnDefinitions.Clear();
            List<TableColumn> cols = this.Columns;
            int count = cols.Count;
            for (int i = 0; i < count; i++)
            {
                TableColumn c = cols[i];
                ColumnDefinition d = new ColumnDefinition();
                d.Width = new GridLength(c.Width);
                g.ColumnDefinitions.Add(d);
            }
            g.ColumnDefinitions.Add(new ColumnDefinition());
        }
        #endregion

        #region Generate Rows
        void GenerateRows()
        {
            Grid g = this;
            g.RowDefinitions.Clear();
            List<TableRow> rows = this.Rows;
            int count = rows.Count;
            for (int i = 0; i < count; i++)
            {
                TableRow r = rows[i];
                RowDefinition d = new RowDefinition();
                d.Height = new GridLength(r.Height >= 0.0 ? r.Height : DefaultRowHeight);
                g.RowDefinitions.Add(d);
            }
            g.RowDefinitions.Add(new RowDefinition());
        }
        #endregion

        #region Generate Cells
        void GenerateCells()
        {
            Grid g = this;
            List<TableColumn> columns = this.Columns;
            List<TableRow> rows = this.Rows;
            int columnCount = columns.Count;
            int rowCount = rows.Count;
            TableCell cell;
            int id;

            for (int row = 0; row < rowCount; row++)
            {
                for (int column = 0; column < columnCount; column++)
                {
                    id = GetCellId(column, row);
                    if (mCells.ContainsKey(id))
                    {
                        #region Cell exists

                        cell = mCells[id];
                        UIElement child = cell.Child;

                        //generate background if necessary
                        if (cell.Background != null)
                        {
                            Rectangle r = new Rectangle();
                            r.Fill = cell.Background;
                            r.SetValue(Grid.ColumnProperty, column);
                            r.SetValue(Grid.RowProperty, row);
                            g.Children.Add(r);
                        }
                        #region Defensive tracing
                        else
                        {
                            //not required, skip
                        }
                        #endregion

                        //set foreground if necessary
                        if (cell.Foreground != null)
                        {
                            if (child is TextBlock) ((TextBlock)child).Foreground = cell.Foreground;
                            else child.SetValue(TextBlock.ForegroundProperty, cell.Foreground);
                        }
                        #region Defensive tracing
                        else
                        {
                            //not required, skip
                        }
                        #endregion

                        //set border if necessary
                        if (cell.Border != null)
                        {
                            cell.Border.SetValue(Grid.ColumnProperty, column);
                            cell.Border.SetValue(Grid.RowProperty, row);
                            g.Children.Add(cell.Border);
                        }
                        #region Defensive tracing
                        else
                        {
                            //not required, skip
                        }
                        #endregion

                        //apply format if necessary
                        if (!string.IsNullOrEmpty(cell.Format))
                        {
                            if (cell.Child is NumberLabel)
                            {
                                ((NumberLabel)cell.Child).Format = cell.Format;
                            }
                        }
                        #region Defensive tracing
                        else
                        {
                            //not required, skip
                        }
                        #endregion

                        //add cell's content to grid
                        if (child != null)
                        {
                            g.Children.Add(child);
                        }
                        #endregion
                    }
                    #region Defensive tracing
                    else
                    {
                        //cell not defined, skip
                    }
                    #endregion
                }
            }
        }
        #endregion

        #region Go To
        public void GoTo(int column, int row)
        {
            mCurrentColumn = column;
            mCurrentRow = row;
        }
        #endregion

        #region Clear All Cells
        public void ClearAllCells()
        {
            mCells.Clear();
        }
        #endregion

        #region On Loaded
        /// <summary>
        /// Initialize this form when form is loaded.
        /// </summary>
        protected virtual void OnLoaded(object sender, RoutedEventArgs e)
        {
            //if (!this.IsDesignMode())
            {



            }
            #region Defensive tracing
            //else
            //{
            //    //do nothing in design-mode
            //}
            #endregion
        }
        #endregion

        #region Constructors
        /// <summary>
        /// Default constructor.
        /// </summary>
        public Table()
        {
            this.Loaded += new RoutedEventHandler(OnLoaded);
            mBorder = new Border();
            mBorder.BorderBrush = mBorderStroke;
            mBorder.BorderThickness = new Thickness(1.0);
            this.Children.Add(mBorder);
        }
        #endregion
    }
}
